'''
Created on Feb 5, 2014

@author: dan
'''
import sys, copy, re
import meronymy  # @UnresolvedImport
from nltk.corpus import verbnet, wordnet
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.stem import PorterStemmer
import pprint

HyperRestr = { 'concrete'     : ['physical_entity.n.01'],
               'abstract'     : ['abstraction.n.06'],
               'communication': ['communication.n.02'],
               'body_part'    : ['body_part.n.01'],
               'animate'      : ['causal_agent.n.01', 'living_thing.n.01'],
               'pointy'       : ['physical_entity.n.01'],
               'refl'         : ['physical_entity.n.01'],
               'solid'        : ['physical_entity.n.01'],
               'organization' : ['group.n.01'],
               'region'       : ['region.n.01'],
               'location'     : ['location.n.01', 'location.n.03'],
               'animal'       : ['animal.n.01'],
               'force'        : ['entity.n.01'],
               
               'Instrument'   : ["instrumentality.n.03", "act.n.02", 'communication.n.02', 'body_part.n.01'],
               'Theme'        : ['entity.n.01'],
               'Actor'        : ['causal_agent.n.01'],
               'Patient'      : ['entity.n.01'],
               'Recipient'    : ['entity.n.01'],
               'Oblique'      : ['entity.n.01'],
               'Location'     : ['location.n.01', 'location.n.03'],
               'Destination'  : ['entity.n.01'],
               'Experiencer'  : ['entity.n.01'],
               'Source'       : ['entity.n.01'],
               'Beneficiary'  : ['entity.n.01'],
               'Agent'        : ['entity.n.01'],
               'Product'      : ['entity.n.01'],
               'Material'     : ['entity.n.01'],
               'Topic'        : ['entity.n.01'],
               'Predicate'    : ['entity.n.01'],
               'Asset'        : ['entity.n.01'],
                }

DictRestr = { 'concrete'    : 'return false',
             'abstract'     : 'return false',
             'communication': 'return false',
             'body_part'    : 'return false',
             'animate'      : 'return false',
             'pointy'       : 'sharp',
             'refl'         : 'return false',
             'solid'        : 'return false',
             'organization' : 'return false',
             'region'       : 'return false',
             'location'     : 'return false',
             'animal'       : 'return false',
             'force'        : 'return false',
             
             'Instrument'   : 'used ',
             'Theme'        : 'return false',
             'Actor'        : 'return false',
             'Patient'      : 'return false',
             'Oblique'      : 'return false',
             'Recipient'    : 'return false',
             'Location'     : 'return false',
             'Destination'  : 'return false',
             'Experiencer'  : 'return false',
             'Source'       : 'return false',
             'Beneficiary'  : 'return false',
             'Agent'        : 'return false',
             'Product'      : 'return false',
             'Material'     : 'return false',
             'Topic'        : 'return false',
             'Predicate'    : 'return false',
             'Asset'        : 'return false',
             }

Pronouns = { 'another'  : 'thing',
             'it'       : 'thing',
             'her'      : 'person',
             'him'      : 'person',
             'them'     : 'group'}

DEBUG = False

class Summarizer:
    report = { "checkVerbNetFull"   : {"right" : 0, "wrong": 0 },
               "checkVerbNetHalf"   : {"right" : 0, "wrong": 0 },
               "be"                 : {"right" : 0, "wrong": 0 },
               "do"                 : {"right" : 0, "wrong": 0 },
               "deverbal"           : {"right" : 0, "wrong": 0 },
               "adverbal"           : {"right" : 0, "wrong": 0 },
               "phrasal"            : {"right" : 0, "wrong": 0 },
               "partmeronymy"       : {"right" : 0, "wrong": 0 },
               "membermeronymy"     : {"right" : 0, "wrong": 0 },
               "substancemeronymy"  : {"right" : 0, "wrong": 0 },
               "relationship"       : {"right" : 0, "wrong": 0 },
               "instrument"         : {"right" : 0, "wrong": 0 },
               "similar"            : {"right" : 0, "wrong": 0 }}
    
    def writewrong(self, sentences):
        for test in Summarizer.report:
            for sentence in sentences:
                if test in sentence and sentence[test] != sentence["stdanswer"]:
                    print sentence["id"],sentence["verb"], sentence["noun1"], \
                          sentence["noun2"], sentence["stdanswer"], test  
                          
    def writeright(self, sentences):
        for test in Summarizer.report:
            for sentence in sentences:
                if test in sentence and sentence[test] == sentence["stdanswer"]:
                    print sentence["id"],sentence["verb"], sentence["noun1"], \
                          sentence["noun2"], sentence["stdanswer"], test 
    
    def writenoguesses(self, sentences):
        for info in ["id", "verb", "noun1", "noun2", "stdanswer"]:
            print info,
        for sentence in sentences:
            noguess = True
            for entry in sentence:
                if entry in Summarizer.report:
                    noguess = False
            if noguess:
                print
                for info in ["id", "verb", "noun1", "noun2", "stdanswer"]:
                    print sentence[info],
    
    def writereport(self, sentences):
        guessed = 0
        for sentence in sentences:
            for test in Summarizer.report:
                if sentence.get(test):
                    guessed += 1
                    if sentence["stdanswer"] == sentence[test]:
                        Summarizer.report[test]["right"] += 1
                    else:
                        Summarizer.report[test]["wrong"] += 1
        for test in Summarizer.report:
            print test, ',', Summarizer.report[test]["right"], ',', Summarizer.report[test]["wrong"]
        print len(sentences), guessed        

''' A class to encapsulate the logic of matching word triples (verb, noun, noun) or pairs (verb, noun)
    with VerbNet classframes.'''
class VNClassFramesWith:
    '''to initialize, it requires a verbnet class and an idea of how many nouns we will compare. The
       quantity of nouns decides whether we match with frames such as: V noun {with} noun
       or V {with} noun. The logic will compile the restrictions relevant for the nouns in such a frame
       and maintain a self.frames field for those restrictions as well as a self.classid identifying the frame.'''
    def __init__(self, vnclass, numOfNouns, prep):
        if(numOfNouns == 1):
            priorTag = 'VERB'
        elif(numOfNouns == 2):
            priorTag = 'NP'
        else: raise ValueError
        self.frames = list()
        self.classid = vnclass.attrib['ID']
        frames = vnclass.find("FRAMES")
        for frame in list(frames):
            parts = list(frame.find("SYNTAX"))
            for i in xrange(0, len(parts) - 1):
                if parts[i].tag == 'PREP' and parts[i - 1].tag == priorTag:
                    if 'value' in parts[i].keys() and \
                      parts[i].attrib['value'].rfind(prep) >= 0:
                        if 'value' in parts[i + 1].keys() and numOfNouns == 2:
                            firstTheme = parts[i - 1].attrib['value']
                            firstSelRest = getSelectionalRestr(vnclass, firstTheme)
                            secondTheme = parts[i + 1].attrib['value']
                            secondSelRest = getSelectionalRestr(vnclass, secondTheme)
                            self.frames.append({'Theme1'    : firstTheme,
                                                 'SelRestr1' : firstSelRest,
                                                 'Theme2'    : secondTheme,
                                                 'SelRestr2' : secondSelRest})
                        elif 'value' in parts[i + 1].keys():
                            theme = parts[i + 1].attrib['value']
                            selRest = getSelectionalRestr(vnclass, theme)
                            self.frames.append({'Theme1'    : theme,
                                                 'SelRestr1' : selRest})
    '''Compare a specific set of nouns to the restrictions found in the initializer'''
    def compareNouns(self, noun1, noun2=None):
        # print noun1, noun2
        results = list()
        for frame in self.frames:
            results1 = checkRestrictions(noun1, frame['Theme1'], frame['SelRestr1'])
            if noun2:
                results2 = checkRestrictions(noun2, frame['Theme2'], frame['SelRestr2'])
            else:
                results2 = None
            # print results1, results2
            result = None
            if results1 and (results2 or not noun2):
                result = {'VerbNetClass': self.classid,
                          'Noun1Theme'  : frame['Theme1'],
                          'Noun1Senses' : results1}
                if results2:
                    result.update({'Noun2Theme'  : frame['Theme1'],
                                   'Noun2Senses' : results2})
                # print result
                results.append(result)
        return results

'''compares a noun to the rules governing the thematic roles and selectional restrictions defined
   dictionary at the top of this file'''
def checkRestrictions(noun, thematic, selectional):
    matchingsenses = set()
    thematicmatch = False
    selectionalmatch = True
    
    synsets = wordnet.synsets(noun, pos=wordnet.NOUN)
    if isNamedEntity(noun):
        synsets = [wordnet.synset('person.n.01'),
                   wordnet.synset('organization.n.01'),
                   wordnet.synset('geographical_area.n.01')]
    for sense in synsets:
        if DEBUG: print sense, thematic
        if checkInheritance(sense, HyperRestr[thematic.rstrip('12')])\
           or checkDefinition(sense, DictRestr[thematic.rstrip('12')]):
            thematicmatch = True    
            
        if len(selectional['restr']) > 0 : 
            selectionalmatch = None
            for restriction in selectional['restr']:
                inheritanceChecks = HyperRestr[restriction[0]]
                dictionaryCheck = DictRestr[restriction[0]] 
                thismatch = checkInheritance(sense, inheritanceChecks) \
                            or checkDefinition(sense, dictionaryCheck)
                if not thismatch and restriction[1] == '-':
                    thismatch = True
                elif thismatch and restriction[1] == '+':
                    thismatch = True
                else: thismatch = False
                if selectionalmatch == None: 
                    selectionalmatch = thismatch
                elif selectional['logic'] == 'or':
                    selectionalmatch = selectionalmatch or thismatch
                else:
                    selectionalmatch = selectionalmatch and thismatch
                if DEBUG: print restriction, selectionalmatch
                
        if selectionalmatch and thematicmatch:
            matchingsenses.add(sense)
    return matchingsenses

'''Checks the wordnet dictionary to see if words matching this sense contain
   a phrase.'''
def checkDefinition(sense, phrase):
    if sense.definition.rfind(phrase) > 0:
        return True
    else:
        return False

'''Does naive testing of the noun to determine if we will consider it a
   named entity.'''
def isNamedEntity(noun):
    senses = wordnet.synsets(noun)
    if not senses:
        return True  # If it's not in WordNet, assume that it's a Named Entity
    elif len(senses) == 1 and not senses[0].hypernyms():
        return True  # Only 1 sense that isn't in the hierarchy. 
                    # Just like Czechoslovakia.
    return False

'''Checks the wordnet hierarchy to see if this sense is a hyponym of
   any of the senses listed in hypernymsList'''
def checkInheritance(sense, hypernymsList):
    if sense.name in hypernymsList:
        return True
    while True:
        try:
            sense = sense.hypernyms().pop()
            # print '\t', sense, hypernymsList
        except:
            break
        if sense.name in hypernymsList:
            # print "RETURNED TRUE"
            return sense
    return False


'''convenience method for debugging'''
def seeFrameSyntax(vnclass):
    frames = vnclass.find("FRAMES")
    for frame in list(frames):
        partstring = ""
        parts = list(frame.find("SYNTAX"))
        for part in parts:
            try:
                partstring = partstring + " " + part.attrib['value']
            except:
                pass
        # print partstring

'''takes in a set of verbnet classids and returns a set that includes
   those same verbnet classids together with all of their inherited
   hypernyms'''
def verbNetSuperclasses(vnclassids):
    superclassids = copy.copy(vnclassids)
    for classid in vnclassids:
        while(re.search('[0-9]-[0-9]$', classid)) :
            classid = classid[0:len(classid) - 2]
            superclassids.insert(0, classid)
    return superclassids

'''finds the selectional restrictions associated with a thematic role
   in a verbnet class.'''
def getSelectionalRestr(vnclass, thematicRole):     
    selectional = list()
    logic = ''
    for themrole in vnclass.find('THEMROLES'):
            if thematicRole == themrole.attrib['type']:
                restrs = themrole.find('SELRESTRS')
                if restrs.keys():
                    logic = restrs.attrib['logic']
                for selrestr in themrole.find('SELRESTRS'):
                    try: 
                       selectional.insert(0, (selrestr.attrib['type'], selrestr.attrib['Value']))
                    except:
                       pass
    return {'logic': logic, 'restr':selectional}

def meronymsenses(sentence):
    noun = sentence["noun1"]
    noun2 = sentence["noun2"]
    part = meronymy.meronymsenses(noun, noun2, meronymy.part) 
    if part:
        sentence["partmeronymy"] = 'N' 
        return part
    member = meronymy.meronymsenses(noun, noun2, meronymy.member)
    if member:
        sentence["membermeronymy"] = 'N'
        return member
    substance = meronymy.meronymsenses(noun, noun2, meronymy.substance)
    if substance:
        sentence["substancemeronymy"] = 'N'
        return substance

'''Determines if verb, noun, noun2 fit in any verbnet frames according to the thematic roles and
   selectional restrictions involved. This only considers verbnet matches which provide a frame
   syntax of style: (a) Verb Noun {with} Noun2  or (b) Verb {with} Noun.
   The output is a dictionary such as:
      {VerbNetClass: <the verbnet class that allows this match and includes the given verb>
       Noun1Theme: <the theme that the first noun matches>
       Noun1Senses: <the senses that include the first noun and match the frame/restrictions>
       Noun2Theme: <the theme that the second noun matches>
       Noun2Senses: <the senses that include the second noun and match the frame/restrictions>}'''
def checkVerbNetHalf(sentence):
    lmtzr = WordNetLemmatizer()
    
    noun = lmtzr.lemmatize(sentence["noun2"].lower(), 'n')
    if noun in Pronouns: noun = Pronouns[noun]
    verb = lmtzr.lemmatize(sentence["verb"].lower(), 'v')
    
    prepUsesResult = list()
    classids = verbnet.classids(lemma=verb)
    superclassids = verbNetSuperclasses(classids)
    for classid in superclassids:
        if DEBUG: print classid
        vnclass = verbnet.vnclass(classid)
        verbnetframeswith = VNClassFramesWith(vnclass, 1, "with")
        prepUsesResult.extend(verbnetframeswith.compareNouns(noun))
    if prepUsesResult:
        sentence["checkVerbNetHalf"] = 'V'
        return prepUsesResult

def checkVerbNetFull(sentence):
    lmtzr = WordNetLemmatizer()
    
    noun = lmtzr.lemmatize(sentence["noun1"].lower(), 'n')
    if noun in Pronouns: noun = Pronouns[noun]
    noun2 = lmtzr.lemmatize(sentence.get("noun2").lower(), 'n')
    if noun2 in Pronouns: noun2 = Pronouns[noun2]
    verb = lmtzr.lemmatize(sentence["verb"].lower(), 'v')
    
    prepUsesResult = list()
    classids = verbnet.classids(lemma=verb)
    superclassids = verbNetSuperclasses(classids)
    for classid in superclassids:
        if DEBUG: print classid
        vnclass = verbnet.vnclass(classid)
        verbnetframeswith = VNClassFramesWith(vnclass, 2)
        prepUsesResult.extend(verbnetframeswith.compareNouns(noun, noun2))
    
    if prepUsesResult:    
        sentence["checkVerbNetFull"] = 'V'
        return prepUsesResult

def adverbal(sentence):
    noun = sentence["noun2"]
    adverbalsenses = set()
    for lemma in wordnet.lemmas(noun):
        if lemma.name == noun:
            adjs = lemma.derivationally_related_forms()
            for adj in adjs:
                if adj.synset.pos == wordnet.ADJ:
                    # for lemma in adj.synset.lemmas:
                        advs = wordnet.synsets(adj.name + 'ly')
                        for adv in advs:
#                             print line
#                             print lemma
#                             print adv
                            adverbalsenses.add(adv)
#     for sense in wordnet.synsets( noun ):
#         if checkInheritance( sense, ["property.n.02"]):
#             adverbalsenses.add( sense )
    if adverbalsenses:
        sentence["adverbal"] = 'V'
        return adverbalsenses

'''Decides whether a noun1 is acting as a verb in a deverbal sense. First, it is necessary to 
   identify if verbs are derivationally related to this noun1. Then, we can check noun2 to see
   if it matches the verbnet selectional restrictions that would be in play if noun1 were turned 
   into a verb and the syntax frame Verb {with} Noun2 was being employed.'''
def deverbal(sentence):
    noun1 = sentence["noun1"]
    noun2 = sentence["noun2"]
    verbs = list()
    for sense in wordnet.synsets(noun1, pos=wordnet.NOUN):
        # print sense
        for lemma in sense.lemmas:
            # print ">", lemma.name
            if lemma.name[0:3] == noun1[0:3]:
                forms = lemma.derivationally_related_forms()
                for form in forms:
                    # print form
                    if form.synset.pos == wordnet.VERB:
                        for lemma in form.synset.lemmas:
                            lemmakey = lemma.key.rstrip(':')
                            verbs.extend(verbnet.classids(wordnetid=lemmakey))
    
    # deverbal = False
    verbs = verbNetSuperclasses(verbs)
    for verb in verbs:
        try:
            frame = VNClassFramesWith(verbnet.vnclass(verb), 1)
            # pp.pprint( frame.frames )
            if frame.compareNouns(noun2):
                # print line
                # print frame.classid
                # print verb
                # deverbal = True
                sentence["deverbal"] = 'N'
                return verb, frame.frames
        except:
            print "Verbnet class not found", verb

'''tests the similarity of the two nouns according to the  Leacock and Chodorow
   similarity algorithm. If the similarity is greater that 2, it returns the
   most similar pair of senses for the two nouns.'''
def similar(sentence):
        noun1senses = wordnet.synsets(sentence["noun1"], pos=wordnet.NOUN)
        noun2senses = wordnet.synsets(sentence["noun2"], pos=wordnet.NOUN)
         
        lch = 0
        for noun1sense in noun1senses:
            for noun2sense in noun2senses:
                temp = noun1sense.lch_similarity(noun2sense)
                if temp > lch:
                    similarsenses = (noun1sense, noun2sense)
                    lch = temp
        if lch > 2.0:
            sentence["similar"] = 'N'
            return similarsenses
        else:
            return False
        
'''Checks to see if a noun fits our criteria for being an instrument.
   This version is broken out to allow identification of useful/helpful
   criteria.'''
def instrument(sentence):
    nounsenses = wordnet.synsets(sentence["noun2"])
    aflag = iflag = bflag = cflag = uflag = tflag = False
    instrsenses = set()
    for sense in nounsenses:
        check = checkInheritance(sense, ["instrumentality.n.03"])
        iflag = iflag or check
        check = checkInheritance(sense, ["act.n.02"])
        aflag = aflag or check
        check = checkInheritance(sense, ['communication.n.02'])
        cflag = cflag or check
        check = checkInheritance(sense, ['body_part.n.01'])
        bflag = bflag or check
        check = checkDefinition(sense, 'used ')
        uflag = uflag or check
        tflag = iflag or aflag or bflag or cflag or uflag
        if tflag:
            instrsenses.add(sense)
    if instrsenses:
        sentence["instrument"] = 'V'
        return instrsenses

'''Checks whether the two words, when put together, make a common verbal phrase
   according to wordnet.'''
def phrasal(sentence):
    ps = PorterStemmer() 
    phrase = ps.stem(sentence["verb"]) + '_' + sentence["noun1"]
    phrasesense = wordnet.synsets(phrase)
    if phrasesense:
        sentence["phrasal"] = 'N'
        return phrasesense
    
def be(sentence):
    '''forms of the verb, to Be'''
    beForms = ['is', 'was', 'be', '\'s', 'am', 'are', 'been']
    if sentence["verb"] in beForms:
        sentence["be"] = 'N'
        return True
    
def do(sentence):
    '''forms of the verb, to Do'''
    doForms = ['do','doing','did']
    if sentence["verb"] in doForms:
        sentence["do"] = 'V'
        return True
    
def relationship( sentence ):
    for sense in wordnet.synsets(sentence["noun1"], pos=wordnet.NOUN):
        if checkInheritance( sense, ["relationship.n.01", "relationship.n.02",\
                                     "relationship.n.03","relationship.n.04"]):
            sentence["relationship"] = 'N'
            return sense 

if __name__ == '__main__':
    pp = pprint.PrettyPrinter(indent=4)
    
    if len(sys.argv) < 2:
        print 'usage: ' + sys.argv[0] + ' input_file'
    else:
        try:
            infile = open(sys.argv[1])
        except IOError as e:
            print "I/O error({0}): {1}".format(e.errno, e.strerror)
            sys.exit()
        
        sentences = list()
        
        tests = [checkVerbNetFull, checkVerbNetHalf, be, do, deverbal,
                 adverbal, phrasal, meronymsenses, relationship, instrument, similar]
        
        for line in infile:
            #sys.stdout.write("Sentence: %s   \r" % (line.rstrip()) )
            #sys.stdout.flush()


            line = line.split(' ')  # take the input apart and assign          
            sentence = {"id"        : line[0],
                        "verb"      : line[1],
                        "noun1"     : line[2],
                        "noun2"     : line[4],
                        "stdanswer" : line[5].rstrip()}
            
            match = None
            i = 0
            while not match and i < len(tests):
                test = tests[i]
                output = test(sentence)
                if output:
                    match = (test, output)
                i += 1 
            sentences.append(sentence)
        summary = Summarizer()
        print "__________________SUMMARY"
        summary.writereport(sentences)
        print "__________________CORRECT"
        summary.writeright(sentences)
        print "__________________INCORRECT"
        summary.writewrong(sentences)
        print "__________________NO GUESS"
        summary.writenoguesses(sentences)
